Element Call einrichten: Verschlüsselte Videoanrufe mit Element X und Matrix Synapse
Seit April 2025 funktionieren Videoanrufe in Element X, Element Web und Element Desktop nicht mehr automatisch – zumindest dann nicht, wenn du deinen eigenen Matrix-Homeserver betreibst. Der Grund: Der bisher mitgelieferte, öffentlich gehostete LiveKit-Dienst von Element wurde eingestellt. Wer Anrufe nutzen möchte, muss das MatrixRTC-Backend nun selbst hosten.
Ein zentrales Element (im wahrsten Sinne) dieser Clients ist nämlich Element Call, das über das MatrixRTC-Backend läuft – inklusive LiveKit. Darüber werden Ende-zu-Ende-verschlüsselte (E2EE) Audio- und Videoanrufe direkt in der App ermöglicht – zuverlässig, mit hoher Qualität und auch über föderierte Server hinweg.
Element Call sorgt nicht nur für klassische 1:1-Gespräche, sondern unterstützt auch Gruppenanrufe, Bildschirmfreigabe, Reaktionen mit Emojis und eine stabile Verbindung – selbst bei größeren Konferenzen. Und genau dieses System musst du dir nun selbst aufsetzen.
Ich habe mein Setup deshalb um genau dieses Backend erweitert – bestehend aus LiveKit und einem kleinen JWT-Auth-Service, der die Verbindung absichert. Weil bei der Einrichtung ein paar Dinge beachtet werden müssen, habe ich alles Schritt für Schritt dokumentiert.
In diesem Beitrag findest du die komplette Einrichtung sowohl für Element X (als App) als auch für Element Web – damit Anrufe plattformübergreifend zuverlässig funktionieren.
❗ Wichtig zu wissen:
Element (Legacy, auch Classic genannt) wird für MatrixRTC nicht mehr unterstützt. Diese ältere Version setzt noch auf Jitsi + TURN, was technisch nicht mehr dem aktuellen Stand entspricht und mittelfristig ausläuft. Was du in Zukunft brauchen wirst, ist Element X.
Damit nutzt du die aktuelle, von Matrix offiziell empfohlene Lösung für Audio- und Videoanrufe – vollständig selbst gehostet und direkt in dein bestehendes System integriert.
🔽 Element gibt’s für alle Plattformen – egal ob Android, iOS, Desktop oder Web.
Du findest alle offiziellen Downloads gebündelt unter: element.io/download
🤖 Was machen LiveKit und der JWT-Service eigentlich?
Bevor wir richtig loslegen, hier ein Überblick über die Komponenten, die du für dein eigenes Element Call Backend brauchst. Keine Sorge – vieles davon hast du wahrscheinlich schon im Einsatz oder lässt sich schnell nachrüsten.
Damit Element Call funktioniert, braucht es zwei zentrale Bausteine: LiveKit und den JWT-Service.
LiveKit ist für die gesamte Audio- und Videoverarbeitung zuständig. Es handelt sich dabei um eine sogenannte SFU (Selective Forwarding Unit), die eingehende Medienströme von Clients empfängt und gezielt an andere Teilnehmende weiterleitet. Das macht Gruppenanrufe effizient, ohne dass jeder Client mit jedem direkt verbunden sein muss. In unserem Setup läuft LiveKit als eigenständiger Docker-Container.
Der JWT-Service übernimmt die Authentifizierung. Er generiert sogenannte JSON Web Tokens (JWT), mit denen sich deine Matrix-Nutzer gegenüber LiveKit ausweisen. Ohne diesen Service würde LiveKit Verbindungen ablehnen – die Tokens sorgen also dafür, dass nur berechtigte Nutzer Zugriff erhalten.
Gemeinsam sorgen beide Dienste dafür, dass Audio- und Videoanrufe über Matrix sicher und zuverlässig ablaufen – selbst über föderierte Server hinweg.
Damit dein Element Call Backend aber reibungslos funktioniert, brauchst du neben den Docker-Containern noch ein paar ergänzende Einstellungen.

⚙️ Voraussetzungen
Bevor du loslegst, sollte deine Umgebung ein paar grundlegende Dinge mitbringen. Falls du schon länger mit Docker und Matrix arbeitest, hast du das meiste davon vermutlich bereits am Start:
- Installierte Docker-Umgebung (inkl. Docker Compose)
- Eingerichteter NGINX Webserver – z. B. mit dem NGINX Proxy Manager (andere Webserver gehen natürlich auch)
- Konfigurierte Firewall, z. B. über UFW, idealerweise mit Kenntnis über dein internes Netzwerk
🚀 Was wir gemeinsam einrichten werden
In diesem Beitrag führe ich dich Schritt für Schritt durch die komplette Einrichtung deines MatrixRTC-Backends – damit Anrufe in Element X und Element Web funktionieren. Das erwartet dich:
- Docker-Container deployen – für LiveKit und JWT-Service
- LiveKit-Konfiguration anlegen – inklusive Medienports und Schlüssel
- Firewall konfigurieren – z. B. mit UFW, damit alles durchkommt
- DNS-Eintrag setzen – z. B.
rtc.matrix.domain.com
→114.221.45.68
- NGINX Proxy einrichten – damit Anfragen an die richtigen Container gehen
- Well-Known-Konfiguration erstellen – damit Matrix-Clients dein Backend finden
- Synapse anpassen – aktivieren der experimentellen Features
- Element Web fit machen – Konfiguration für MatrixRTC im Webclient aktivieren
Wenn du diese Schritte einmal durchgehst, hast du am Ende dein eigenes, voll funktionsfähiges Element Call Backend – komplett selbst gehostet und bereit für sichere, moderne Sprach- und Videoanrufe über Matrix.
⚠️Hinweis:
Alle in diesem Beitrag gezeigten Tokens, Secrets, IPs und Domains wurden ausschließlich zu Dokumentationszwecken neu erzeugt. Sie spiegeln nicht die tatsächlichen Daten meines Produktivsystems wieder.
Also. los geht’s!
🔐 Schlüssel generieren (64 Zeichen)
Für die Kommunikation zwischen LiveKit und JWT-Service wird ein Value und ein Secret benötigt. Führe den Befehl also zwei Mal aus und schreibe dir das Value und das Secret irgendwo auf:
tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 64
🐳 Docker Compose
Hier trägst du das Value und das Secret direkt ein.
services: matrix-element-call-jwt: image: ghcr.io/element-hq/lk-jwt-service:latest container_name: matrix-element-call-jwt hostname: matrix-element-call-jwt environment: - LK_JWT_PORT=8080 - LIVEKIT_URL=https://rtc.matrix.domain.com/livekit/sfu - LIVEKIT_KEY=oLtjBM2NS3u8zU0zxzmktgswufzOQF5uNXVm2nuB9HuuCNRu03zvkaqniguotdxY - LIVEKIT_SECRET=zKgSeb0kvkdfgm68wfDXax3PcwwVaGCkRpSXKDeYy4ydTLbxRlUxKqWjZcQEfvqa - LIVEKIT_LOCAL_HOMESERVERS=domain.com networks: dockernet: ipv4_address: 172.16.0.181 restart: unless-stopped ports: - :8080 matrix-element-call-livekit: image: livekit/livekit-server:latest container_name: matrix-element-call-livekit hostname: matrix-element-call-livekit command: --dev --config /etc/livekit.yaml networks: dockernet: ipv4_address: 172.16.0.180 ports: - :7880/tcp - 7881:7881/tcp - 7882:7882/tcp - 50100-50200:50100-50200/udp restart: unless-stopped volumes: - ./data/matrix-element-call-livekit/config.yaml:/etc/livekit.yaml:ro networks: dockernet: external: true
Ein kleiner Hinweis zur folgenden Umgebungsvariable:
- LIVEKIT_LOCAL_HOMESERVERS=domain.com
Diese Einstellung ist Teil einer noch nicht final veröffentlichten Funktion von LiveKit. Sie sorgt künftig dafür, dass nur Benutzer deines eigenen Homeservers neue Anrufe starten dürfen. Nutzer von föderierten Homeservern können sich weiterhin in bestehende Anrufe einklinken – aber keine neuen erstellen.
Das ist vor allem dann nützlich, wenn du deinen Dienst öffentlich anbietest, aber die Kontrolle darüber behalten möchtest, wer tatsächlich Anrufe initiieren darf. Bis die Funktion offiziell aktiv ist, hat dieser Eintrag noch keine Auswirkung – du kannst ihn aber bereits setzen, um vorbereitet zu sein.
So erlaubst du mehreren Homeservern das Initiieren von Anrufen:
- LIVEKIT_LOCAL_HOMESERVERS=domain.com,example.com
📝 LiveKit-Konfiguration anlegen
mkdir -p ./data/matrix-element-call-livekit && nano ./data/matrix-element-call-livekit/config.yaml
Inhalt:
port: 7880 bind_addresses: - "0.0.0.0" rtc: tcp_port: 7881 port_range_start: 50100 port_range_end: 50200 use_external_ip: false ips: includes: - 114.221.45.68/32 logging: level: debug turn: enabled: false domain: localhost cert_file: "" key_file: "" tls_port: 5349 udp_port: 443 external_tls: true keys: oLtjBM2NS3u8zU0zxzmktgswufzOQF5uNXVm2nuB9HuuCNRu03zvkaqniguotdxY: "zKgSeb0kvkdfgm68wfDXax3PcwwVaGCkRpSXKDeYy4ydTLbxRlUxKqWjZcQEfvqa"
Weitere Einstellungen kannst du dem Konfigurationsbeispiel aus dem Github Repository entnehmen.
Das Value und das Secret, welche du vorhin erzeugt hast, werden hier ebenfalls eingetragen.
🔍 Hinweis zur Netzwerkeinstellung
Standardmäßig empfiehlt LiveKit, den Wert use_external_ip
auf true
zu setzen. In diesem Fall kann der Abschnitt ips:
komplett entfallen, da LiveKit die externe IP automatisch ermittelt.
In meinem Setup hat das allerdings nicht zuverlässig funktioniert, da ich mit einem Docker-Bridge-Netzwerk hinter einer Firewall arbeite. Daher musste ich use_external_ip: false
setzen und meine öffentliche IP explizit über ips.includes
eintragen.
Welche Variante bei dir funktioniert, hängt stark von deiner Umgebung ab – insbesondere davon, wie Docker und die Netzwerkstruktur aufgebaut sind. Am besten testest du beide Varianten und beobachtest das Verhalten beim Verbindungsaufbau.
🌐 Docker-Netzwerk erstellen (optional)
docker network create --subnet=172.16.0.0/24 dockernet
Wenn das Docker Netzwerk nicht erstellt wird, ist drauf zu achten, dass der Teil in der Docker-Compose auch rausgenommen wird.
🔥 Firewall konfigurieren – Beispiel mit UFW
Je nach Setup und Hosting-Umgebung wird die Netzwerksicherheit unterschiedlich gehandhabt. Manche nutzen eine vorgeschaltete Cloud-Firewall (z. B. bei Hetzner oder Ionos), andere – so wie ich – setzen direkt auf dem VPS eine lokale Firewall ein, beispielsweise mit UFW (Uncomplicated Firewall).
Da ich in meinem Setup Docker-Container in einem eigenen Subnetz verwende, habe ich zusätzlich docker-ufw im Einsatz, um gezielt Zugriffe zwischen Host und Containern zu regeln.
Hier ein Beispiel, wie du das mit UFW umsetzen kannst:
sudo ufw route allow proto tcp from any to 172.16.0.180 port 7881 comment "Matrix LiveKit TCP" sudo ufw route allow proto tcp from any to 172.16.0.180 port 7882 comment "Matrix LiveKit TCP" sudo ufw route allow proto udp from any to 172.16.0.180 port 50100:50200 comment "Matrix LiveKit UDP Media Range"
Diese Regeln erlauben eingehenden Verkehr auf die relevanten Ports im internen Docker-Netzwerk – in diesem Fall für den LiveKit-Dienst.
🚪 Zusätzliche Ports für den Zugriff von außen
Wenn du einen Reverse Proxy wie NGINX Proxy Manager verwendest, achte darauf, dass die Standard-HTTP(S)-Ports offen sind:
- Port 80 (HTTP)
- Port 443 (HTTPS)
Der NGINX Proxy Manager selbst ist im Browser typischerweise unter Port 81 erreichbar – das ist nur für die Verwaltungsschnittstelle relevant und muss nicht öffentlich freigegeben werden, solange du lokal arbeitest.
🧭 DNS-Eintrag setzen
Ein A-Record reicht aus:
rtc.matrix 7200 IN A 114.221.45.68
🔄 NGINX Proxy Manager: Weiterleitungen einrichten
Die Weiterleitungen für den JWT-Service und LiveKit legst du im Bereich „Advanced“ der Subdomain-Konfiguration im NGINX Proxy Manager an.
Dabei gilt es einen wichtigen Punkt zu beachten:
⚠️ Hinweis zu Zertifikaten und Subdomains
Wenn du eine Subdomain wie rtc.matrix.domain.com
verwendest, kannst du kein Wildcard-Zertifikat für *.domain.com
verwenden – dieses deckt nur eine Ebene ab (also z. B. matrix.domain.com
, mail.domain.com
etc.).
Subdomains mit mehr als einer Ebene, wie rtc.matrix.domain.com
, fallen nicht unter ein einfaches Wildcard-Zertifikat.
✅ Lösungsmöglichkeiten:
- Ändere die Subdomain, z. B. zu
rtc-matrix.domain.com
– das funktioniert mit*.domain.com
- Oder: Erzeuge ein separates Let’s Encrypt-Zertifikat für
rtc.matrix.domain.com
direkt über den NGINX Proxy Manager
Warum das so ist?
Wildcard-Zertifikate wie *.domain.com
decken nur eine Subdomain-Ebene ab. Für tiefer verschachtelte Domains (*.matrix.domain.com
) bräuchtest du ein eigenes Wildcard-Zertifikat für diese Struktur – was mit Let’s Encrypt nicht ohne weiteres geht.


location ^~ /livekit/jwt/ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://172.16.0.181:8080/; } location ^~ /livekit/sfu/ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_send_timeout 120; proxy_read_timeout 120; proxy_buffering off; proxy_set_header Accept-Encoding gzip; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://172.16.0.180:7880/; }
Denke bitte daran, das Netzwerkprotokoll „Websocket“ zu aktivieren.
🧩 Well-Known-Konfiguration
Ein wichtiger Teil der Einrichtung ist der .well-known
-Eintrag. Er sagt Matrix-Clients wie ElementX, wo dein Homeserver erreichbar ist – und zusätzlich, wie sie dein LiveKit-Backend für Anrufe nutzen können.
Dabei ist entscheidend, auf welcher Domain dieser Eintrag bereitgestellt wird – und das hängt direkt vom server_name
ab, den du in deiner homeserver.yaml
konfiguriert hast.
🔍 So bestimmst du die richtige Domain
Wenn dein Matrix-Server in der homeserver.yaml
den Eintragserver_name: domain.com
enthält,
→ dann muss der .well-known
-Eintrag auf der Hauptdomain https://domain.com
liegen.
Wenn dein server_name
stattdessenserver_name: matrix.domain.com
,
lautet,
→ dann gehört der Eintrag natürlich auf die Subdomain https://matrix.domain.com
.
location /.well-known/matrix/client { default_type application/json; add_header Content-Type application/json; add_header "Access-Control-Allow-Origin" *; return 200 '{"m.homeserver": {"base_url": "https://matrix.domain.com"}, "m.identity_server": {"base_url": "https://vector.im"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://rtc.matrix.domain.com/livekit/jwt"}, {"type": "nextgen_new_foci_type", "props_for_nextgen_foci": "val"}]}'; }

Wenn du dir nicht sicher bist, auf welche Domain der Eintrag gehört: Schaue in deiner homeserver.yaml
unter server_name:
– diese Angabe bestimmt die Ziel-Domain für den .well-known
-Pfad.
Sobald deine Konfiguration aktiv ist, kannst du den .well-known
-Pfad ganz einfach testen – direkt über die Kommandozeile:
curl https://domain.com/.well-known/matrix/client
Die Ausgabe sollte in etwa so aussehen:
{"m.homeserver": {"base_url": "https://matrix.domain.com"}, "m.identity_server": {"base_url": "https://vector.im"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://rtc.matrix.domain.com/livekit/jwt"}, {"type": "nextgen_new_foci_type", "props_for_nextgen_foci": "val"}]}
Wenn du eine solche Rückgabe bekommst, ist alles richtig eingerichtet. Andernfalls prüfe bitte die Domain, die JSON-Syntax und ob dein Webserver die Datei korrekt ausliefert.
🛠️ Homeserver.yaml erweitern
Für Synapse müssen ein paar experimentelle Features aktiviert bzw. ergänzt werden:
experimental_features: msc3266_enabled: true msc4222_enabled: true msc4140_enabled: true max_event_delay_duration: 24h rc_message: per_second: 0.5 burst_count: 30 rc_delayed_event_mgmt: per_second: 1 burst_count: 20
Danach einmal die Services (neu)starten – das sollte es eigentlich gewesen sein.
🧪 Element Web fit machen für MatrixRTC
Wenn du Element Web verwendest und dort ebenfalls direkt über MatrixRTC telefonieren möchtest, musst du eine kleine zusätzliche Konfiguration vornehmen.
Standardmäßig nutzt Element Web nämlich noch den klassischen „Legacy“-Anrufweg – also Jitsi in Kombination mit TURN. Das funktioniert zwar auch, vorausgesetzt, es ist konfiguriert, ist aber technisch nicht so tief in Matrix integriert und fühlt sich eher wie ein externer Anrufdienst an.
Seit einiger Zeit unterstützt Element Web jedoch nativ das MatrixRTC-Backend mit LiveKit und JWT – also genau das Setup, das du in dieser Anleitung eingerichtet hast. Damit das auch im Web-Client aktiv wird, musst du in der Konfigurationsdatei von Element bestimmte Optionen setzen.
⚙️ Konfiguration in Config.json
Öffne die Datei config.json
deiner Element Web-Instanz und ergänze (oder passe) folgenden Block an:
"features": { "feature_video_rooms": true, "feature_group_calls": true, "feature_element_call_video_rooms": true }
📁 Die Datei liegt bei einem Docker-Setup meist im Verzeichnis /app/config.json
oder je nach Containerstruktur an anderer Stelle.
🎥 Wo finde ich die neue Anruffunktion?
Nach dem Neustart findest du im Chat-Fenster (oben rechts) einen Kamera-Button.
Wenn du diesen klickst, öffnet sich ein Dialog – hier kannst du auswählen:
- Legacy-Anruf (Jitsi, TURN-basiert)
- Element Call (Beta) – das ist dein neues MatrixRTC-gestütztes LiveKit-Backend 🎉

✅ Fazit
Mit ein paar gezielten Schritten lässt sich das eigene MatrixRTC-Backend zuverlässig betreiben – dank Docker, LiveKit, JWT-Service und einem sauber eingerichteten Proxy. Ich hoffe, diese Anleitung hilft dir beim Einrichten deines eigenen Element Call Backends und erspart dir ein bisschen Sucherei.
An dieser Stelle möchte ich mich bei der Matrix-Community bedanken, insbesondere bei der Gruppe
👉 #webrtc:matrix.org
,
die mir bei einigen Fragen schnell und freundlich weitergeholfen hat.
Wenn du dich ebenfalls mit dem Thema beschäftigst, lohnt sich ein Blick dorthin auf jeden Fall!
Falls du noch Fragen hast oder irgendwo hängst, schreib mir gerne über die Kommentarfunktion – ich schaue regelmäßig rein.
Oder schau einfach direkt in meine Matrix-Community vorbei:
👉 #community:techniverse.net
Dort tauschen wir uns zu Matrix, Selfhosting und allem Drumherum aus – du bist herzlich willkommen!

Ich wünsche dir viel Erfolg bei der Einrichtung!
2 Kommentare